package cn.uc.ucgc.community.upgrade.conf;

import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;

import org.dom4j.Document;
import org.dom4j.DocumentException;
import org.dom4j.Element;
import org.dom4j.io.SAXReader;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import cn.uc.ucgc.community.upgrade.AppFiles;
import cn.uc.ucgc.community.upgrade.Constants;
import cn.uc.ucgc.community.upgrade.Task;
import cn.uc.ucgc.community.upgrade.Utils;
import cn.uc.ucgc.community.upgrade.conf.DeleteJob.DeleteItem;
import cn.uc.ucgc.community.upgrade.conf.ReplaceJob.ReplaceItem;
import cn.uc.ucgc.community.upgrade.conf.task.AddTask;
import cn.uc.ucgc.community.upgrade.conf.task.DeleteTask;
import cn.uc.ucgc.community.upgrade.conf.task.FullReplaceTask;
import cn.uc.ucgc.community.upgrade.conf.task.ModifyTask;
import cn.uc.ucgc.community.upgrade.conf.task.ReplaceTask;

/**
 * 脚本扩展管理
 * 
 * @author yuyj@ucweb.com
 * @createDate 2014-1-3
 *
 */
public class ConfDealMgr {
    private static final Logger logger = LoggerFactory.getLogger(ConfDealMgr.class);
    private static final ConfDealMgr instance = new ConfDealMgr();
    private static final String UPGRADE_MACRO_PATH_KEY = "upgrade.macro";
    /*宏配置文件，可以通过参数指定-Dupgrade.macro=文件绝对路径*/
    private File macroFile;
    
    /*全量替换的任务*/
    private Task fullReplaceTask;
    /*替换的任务*/
    private Task replaceTask;
    /*修改的任务*/
    private ModifyTask modifyTask;
    /*删除任务*/
    private Task deleteTask;
    /*添加任务表*/
    private AddTask addTask;
    
    /*修正阶段：任务*/
    private ConfDealMgr() {
    };

    public static ConfDealMgr getInstance() {
        return instance;
    }

    /**
     * 初始化任务配置列表，如果有修改的任务列表，
     * 在此过程中会要求用户输入对应的宏实际环境信息
     * @throws DocumentException 
     * @throws IOException 
     */
    public void initTask(boolean bCollectMacroItems)
            throws DocumentException, IOException {
        //1)解析替换任务
        File replaceFile = new File(AppFiles.confHomeFile, "conf/replace.xml");
        if (!replaceFile.exists()) {
            logger.warn("no replace conf file:{}", replaceFile.getAbsolutePath());
        } else {
            //1.1)解析全量任务
            //收集数据
            SAXReader reader = new SAXReader();
            Document dom = reader.read(replaceFile);
            List<?> nodes = dom.selectNodes(Constants.REPLACE_W_ITEM_PATH);
            if (null == nodes || nodes.isEmpty()) {
                logger.warn("no wholes replace task");
            } else {
                List<FullReplaceJob> needDoJobs = new ArrayList<FullReplaceJob>();
                for (Object node : nodes) {
                    Element ele = (Element) node;
                    //读取源配置文件路径
                    String srcConfPath = ele.attributeValue(Constants.REPLACE_ITEM_SRCPATH);
                    if (null == srcConfPath || srcConfPath.isEmpty()) {
                        //源路径必须配置，否则忽略
                        logger.warn("you must config srcPath in replace.xml");
                        continue;
                    }
                    //判断对应的源配置文件是否存在
                    File srcConfFile = new File(AppFiles.oldVerHomeFile, srcConfPath);
                    if (!srcConfFile.exists()) {
                        logger.warn("{} must exist", srcConfFile.getAbsolutePath());
                        continue;
                    }
                    //读取目标配置文件路径
                    String dstConfPath = ele.attributeValue(Constants.REPLACE_ITEM_DSTPATH,
                            srcConfPath);
                    File dstConfFile = new File(AppFiles.newVerHomeFile, dstConfPath);
                    if (!dstConfFile.exists()) {
                        logger.warn("{} must exist", dstConfFile.getAbsolutePath());
                        continue;
                    }
                    
                    recursionGetFullRJob(needDoJobs, srcConfFile, dstConfFile);
                    
                }
                if (needDoJobs.isEmpty()) {
                    logger.warn("no wholes replace task to do");
                } else {
                    this.fullReplaceTask = new FullReplaceTask(needDoJobs);
                }
            }

            List<ReplaceJob> replaceJobs = new ArrayList<ReplaceJob>();
            //1.2）解析非全量替换任务
            //prop 节点
            nodes = dom.selectNodes(Constants.REPLACE_PROP_ITEM_PATH);
            parseReplaceJob(replaceJobs, nodes, JobType.PROP);
            //xml 节点
            nodes = dom.selectNodes(Constants.REPLACE_XML_ITEM_PATH);
            parseReplaceJob(replaceJobs, nodes, JobType.XML);
            //查看有无需要替换的任务
            if (replaceJobs.isEmpty()) {
                logger.warn("no replace task to do");
            } else {
                this.replaceTask = new ReplaceTask(replaceJobs);
            }
        }

        //2判读是否有需要修改的任务
        File modifyFile = new File(AppFiles.confHomeFile, "conf/modify.xml");
        if (!modifyFile.exists()) {
            logger.warn("no modify conf file:{}", modifyFile.getAbsolutePath());
        } else {
            //检查宏配置文件
            checkMacroCfgFile();
            //解析
            SAXReader reader = new SAXReader();
            Document dom = reader.read(modifyFile);
            //prop任务
            List<?> nodes = dom.selectNodes(Constants.MODIFY_PROP_ITEM_PATH);
            List<ModifyJob> modifyJobs = new ArrayList<ModifyJob>();
            //设置系统输出流至文件
            parseModifyJob(modifyJobs, nodes, JobType.PROP);
            //xml任务
            nodes = dom.selectNodes(Constants.MODIFY_XML_ITEM_PATH);
            parseModifyJob(modifyJobs, nodes, JobType.XML);
            if (modifyJobs.isEmpty()) {
                logger.warn("no modify task to do");
            } else {
                //将数据写入macro文件
                this.modifyTask = new ModifyTask(modifyJobs, macroFile);
            }
        }
        
        //3判断是否需要删除任务
        File deleteFile = new File(AppFiles.confHomeFile, "conf/delete.xml");
        if (!deleteFile.exists()) {
            logger.warn("no delete conf file:{}", deleteFile.getAbsolutePath());
        } else {
            SAXReader reader = new SAXReader();
            Document dom = reader.read(deleteFile);
            //prop任务
            List<?> nodes = dom.selectNodes(Constants.DELETE_PROP_ITEM_PATH);
            List<DeleteJob> deleteJobs = new ArrayList<DeleteJob>();
            
            parseDeleteJob(deleteJobs, nodes, JobType.PROP);
            //xml任务
            nodes = dom.selectNodes(Constants.DELETE_XML_ITEM_PATH);
            parseDeleteJob(deleteJobs, nodes, JobType.XML);
            if (deleteJobs.isEmpty()) {
                logger.warn("no delete task to do");
            } else {
                this.deleteTask = new DeleteTask(deleteJobs);
            }
        }
        
        //4判断是否需要添加任务
        File addFile = new File(AppFiles.confHomeFile, "conf/add.xml");
        if (!addFile.exists()) {
            logger.warn("no add conf file:{}", addFile.getAbsolutePath());
        } else {
            //检查宏配置文件
            checkMacroCfgFile();
            //解析
            SAXReader reader = new SAXReader();
            Document dom = reader.read(addFile);
            //prop任务
            List<?> nodes = dom.selectNodes(Constants.ADD_PROP_ITEM_PATH);
            List<AddJob> addJobs = new ArrayList<AddJob>();
            parseAddJob(addJobs, nodes, JobType.PROP);
            if (addJobs.isEmpty()) {
                logger.warn("no add task to do");
            } else {
                this.addTask = new AddTask(addJobs, macroFile);
            }
        }
        
        if(bCollectMacroItems)
        {
            List<MacroItem> allMacroItems = new ArrayList<MacroItem>();
            //收集所有宏配置，写到示例文件中
            if (null != modifyTask) {
                List<ModifyJob> modifyJobs = modifyTask.getJobs();
                if (null != modifyJobs) {
                    for (ModifyJob job : modifyJobs) {
                        if (null == job) {
                            continue;
                        }
                        List<MacroItem> macroItems = job.getModifyItems();
                        if (null != macroItems && !macroItems.isEmpty()) {
                            allMacroItems.addAll(macroItems);
                        }
                    }
                }
            }

            if (null != addTask) {
                List<AddJob> addJobs = addTask.getJobs();
                if (null != addJobs) {
                    for (AddJob job : addJobs) {
                        if (null == job) {
                            continue;
                        }
                        List<MacroItem> macroItems = job.getAddItems();
                        if (null != macroItems && !macroItems.isEmpty()) {
                            allMacroItems.addAll(macroItems);
                        }
                    }
                }
            }
            
            File parent = macroFile.getParentFile();
            File macroExampleFile = new File(parent,"macro_example.properties");
            if(!macroExampleFile.exists())
            {
                macroExampleFile.createNewFile();
            }
            logger.warn("Collect macro keys to file:{}",macroExampleFile.getAbsoluteFile());
            Utils.saveProp2File(macroExampleFile, allMacroItems);
        }
    }
    
    private void checkMacroCfgFile() throws IOException
    {
        if (macroFile != null) {
            return;
        }
        String macroFilePath = System.getProperty(UPGRADE_MACRO_PATH_KEY);
        if (null != macroFilePath && !macroFilePath.isEmpty()) {
            macroFile = new File(macroFilePath);
            if (!macroFile.exists() || !macroFile.isFile()) {
                logger.warn("{} not exits,user default!", macroFilePath);
                macroFile = null;
            }
        }
        if (null == macroFile) {
            //获取宏配置文件，如果不存在就新建
            macroFile = new File(AppFiles.confHomeFile, "conf/macro.properties");
            if (!macroFile.exists()) {
                macroFile.createNewFile();
                logger.info("{} not exist,create it", macroFile.getAbsolutePath());
            }
        }
    }
    

    private void parseAddJob(List<AddJob> addJobs, List<?> nodes, JobType type) throws IOException {
        if (null == nodes || nodes.isEmpty()) {
            logger.warn("no {} add task", type);
            return;
        }

        //解析prop任务
        for (Object node : nodes) {
            Element ele = (Element) node;
            //读取源配置文件路径
            String confPath = ele.attributeValue(Constants.ADD_ITEM_SRCPATH);
            if (null == confPath || confPath.isEmpty()) {
                //源路径必须配置，否则忽略
                logger.warn("you must config path in add.xml");
                continue;
            }
            //判断对应的源配置文件是否存在
            File confFile = new File(AppFiles.newVerHomeFile, confPath);
            if (!confFile.exists()) {
                logger.warn("{} must exist", confFile.getAbsolutePath());
                continue;
            }
            //解析需要做添加处理的key
            List<MacroItem> aItems = collectAddItems(ele);
            if (aItems.isEmpty()) {
                logger.warn("no add item to do");
                continue;
            }
            AddJob aJob = new AddJob(type, confFile, aItems);
            addJobs.add(aJob);
        }
    }
    
    private void parseDeleteJob(List<DeleteJob> deleteJobs, List<?> nodes, JobType type) throws IOException {
        if (null == nodes || nodes.isEmpty()) {
            logger.warn("no {} modify task", type);
            return;
        }

        //解析prop任务
        for (Object node : nodes) {
            Element ele = (Element) node;
            //读取源配置文件路径
            String confPath = ele.attributeValue(Constants.DELETE_ITEM_SRCPATH);
            if (null == confPath || confPath.isEmpty()) {
                //目标路径必须配置，否则忽略
                logger.warn("you must config path in delete.xml");
                continue;
            }
            //判断对应的目标配置文件是否存在
            File confFile = new File(AppFiles.newVerHomeFile, confPath);
            if (!confFile.exists()) {
                logger.warn("{} must exist", confFile.getAbsolutePath());
                continue;
            }
            //解析需要做删除处理的key
            List<DeleteItem> dItems = collectDeleteItems(ele);
            if (dItems.isEmpty()) {
                logger.warn("no delete item to do");
                continue;
            }
            DeleteJob dJob = new DeleteJob(type, confFile, dItems);
            deleteJobs.add(dJob);
        }
    }
    
    private void parseModifyJob(List<ModifyJob> modifyJobs, List<?> nodes, JobType type) throws IOException {
        if (null == nodes || nodes.isEmpty()) {
            logger.warn("no {} modify task", type);
            return;
        }

        //解析prop任务
        for (Object node : nodes) {
            Element ele = (Element) node;
            //读取源配置文件路径
            String confPath = ele.attributeValue(Constants.MODIFY_ITEM_SRCPATH);
            if (null == confPath || confPath.isEmpty()) {
                //源路径必须配置，否则忽略
                logger.warn("you must config path in modify.xml");
                continue;
            }
            //判断对应的源配置文件是否存在
            File confFile = new File(AppFiles.newVerHomeFile, confPath);
            if (!confFile.exists()) {
                logger.warn("{} must exist", confFile.getAbsolutePath());
                continue;
            }
            //解析需要做替换处理的key
            List<MacroItem> mItems = collectModifyItems(ele);
            if (mItems.isEmpty()) {
                logger.warn("no modify item to do");
                continue;
            }
            ModifyJob mJob = new ModifyJob(type, confFile, mItems);
            modifyJobs.add(mJob);
        }
    }

    private void parseReplaceJob(List<ReplaceJob> replaceJobs, List<?> nodes, JobType type) {
        if (null == nodes || nodes.isEmpty()) {
            logger.warn("no {} replace task", type);
        } else {
            //解析prop任务
            for (Object node : nodes) {
                Element ele = (Element) node;
                //读取源配置文件路径
                String srcConfPath = ele.attributeValue(Constants.REPLACE_ITEM_SRCPATH);
                if (null == srcConfPath || srcConfPath.isEmpty()) {
                    //源路径必须配置，否则忽略
                    logger.warn("you must config srcPath in replace.xml");
                    continue;
                }
                //判断对应的源配置文件是否存在
                File srcConfFile = new File(AppFiles.oldVerHomeFile, srcConfPath);
                if (!srcConfFile.exists()) {
                    logger.warn("{} must exist", srcConfFile.getAbsolutePath());
                    continue;
                }
                //读取目标配置文件路径
                String dstConfPath = ele
                        .attributeValue(Constants.REPLACE_ITEM_DSTPATH, srcConfPath);
                File dstConfFile = new File(AppFiles.newVerHomeFile, dstConfPath);
                if (!dstConfFile.exists()) {
                    logger.warn("{} must exist", dstConfFile.getAbsolutePath());
                    continue;
                }
                //解析需要做替换处理的key
                List<ReplaceItem> rItems = collectReplaceItems(ele);
                if (rItems.isEmpty()) {
                    logger.warn("no replaceItem key to do");
                    continue;
                }
                ReplaceJob rJob = new ReplaceJob(type, srcConfFile, dstConfFile, rItems);
                replaceJobs.add(rJob);
            }
        }
    }

    private List<MacroItem> collectAddItems(Element ele)
            throws IOException {
        List<MacroItem> rstLst = new ArrayList<MacroItem>();
        List<?> keyNodes = ele.selectNodes(Constants.ADD_KEY_PATH);
        if (null == keyNodes || keyNodes.isEmpty()) {
            logger.warn("no key need add");
            return rstLst;
        }
        for (Object key : keyNodes) {
            Element eleKey = (Element) key;
            //读取源配置文件路径
            String name = eleKey.attributeValue(Constants.ADD_KEY_NAME);
            if (null == name || name.isEmpty()) {
                //路径必须配置，否则忽略
                logger.warn("you must config name in add.xml");
                continue;
            }
            String macroKey = Utils.getMacroKey(eleKey
                    .attributeValue(Constants.ADD_KEY_MACRO_VAL));
            if (null == macroKey || macroKey.isEmpty()) {
                logger.warn("you must config macro value(like ${msg_url}) in add.xml");
                continue;
            }
            String tip = eleKey.attributeValue(Constants.ADD_KEY_TIP);
            String example = eleKey.attributeValue(Constants.ADD_KEY_EXAMPLE);
            
            MacroItem item = new MacroItem(name, macroKey, tip, example);
            rstLst.add(item);
        }
        return rstLst;
    }
    
    private List<DeleteItem> collectDeleteItems(Element ele)
            throws IOException {
        List<DeleteItem> rstLst = new ArrayList<DeleteItem>();
        List<?> keyNodes = ele.selectNodes(Constants.DELETE_KEY_PATH);
        if (null == keyNodes || keyNodes.isEmpty()) {
            logger.warn("no key need delete");
            return rstLst;
        }
        for (Object key : keyNodes) {
            Element eleKey = (Element) key;
            //读取目标配置文件路径
            String name = eleKey.attributeValue(Constants.DELETE_KEY_NAME);
            if (null == name || name.isEmpty()) {
                //路径必须配置，否则忽略
                logger.warn("you must config name in delete.xml");
                continue;
            }

            DeleteItem item = new DeleteItem(name);
            rstLst.add(item);
        }
        return rstLst;
    }
    
    private List<MacroItem> collectModifyItems(Element ele)
            throws IOException {
        List<MacroItem> rstLst = new ArrayList<MacroItem>();
        List<?> keyNodes = ele.selectNodes(Constants.MODIFY_KEY_PATH);
        if (null == keyNodes || keyNodes.isEmpty()) {
            logger.warn("no key need modify");
            return rstLst;
        }
        for (Object key : keyNodes) {
            Element eleKey = (Element) key;
            //读取源配置文件路径
            String name = eleKey.attributeValue(Constants.MODIFY_KEY_NAME);
            if (null == name || name.isEmpty()) {
                //路径必须配置，否则忽略
                logger.warn("you must config name in modify.xml");
                continue;
            }
            String macroKey = Utils.getMacroKey(eleKey
                    .attributeValue(Constants.MODIFY_KEY_MACRO_VAL));
            if (null == macroKey || macroKey.isEmpty()) {
                logger.warn("you must config macro value(like ${msg_url}) in modify.xml");
                continue;
            }
            String tip = eleKey.attributeValue(Constants.MODIFY_KEY_TIP);
            String example = eleKey.attributeValue(Constants.MODIFY_KEY_EXAMPLE);
            
            MacroItem item = new MacroItem(name, macroKey, tip, example);
            rstLst.add(item);
        }
        return rstLst;
    }

    private List<ReplaceItem> collectReplaceItems(Element ele) {
        List<ReplaceItem> rstLst = new ArrayList<ReplaceJob.ReplaceItem>();
        List<?> keyNodes = ele.selectNodes(Constants.REPLACE_KEY_PATH);
        if (null == keyNodes || keyNodes.isEmpty()) {
            logger.warn("no key need replace");
            return rstLst;
        }
        for (Object key : keyNodes) {
            Element eleKey = (Element) key;
            //读取源配置文件路径
            String srcKey = eleKey.attributeValue(Constants.REPLACE_KEY_SRCPATH);
            if (null == srcKey || srcKey.isEmpty()) {
                //源路径必须配置，否则忽略
                logger.warn("you must config srcKey in replace.xml");
                continue;
            }
            String dstKey = eleKey.attributeValue(Constants.REPLACE_KEY_DSTPATH, srcKey);
            //
            ReplaceItem item = new ReplaceItem(srcKey, dstKey);
            rstLst.add(item);
        }
        return rstLst;
    }

    /**
     * 递归获取全局替换任务
     * @param needDoJobs
     * @param srcConfFile
     * @param dstConfFile
     */
    private void recursionGetFullRJob(List<FullReplaceJob> needDoJobs, File srcConfFile, File dstConfFile) {
        
        /**
         * 如果原路径跟目标路径都为文件而不是目录，直接添加一个全局替换任务。
         */
        if(srcConfFile.isFile() && dstConfFile.isFile()) {
            FullReplaceJob fullRJob = new FullReplaceJob(srcConfFile, dstConfFile);
            needDoJobs.add(fullRJob);
            return;
        }
        
        /**
         * 如果原路径为目录，而目标路径为文件，则不符合替换条件。
         */
        if( srcConfFile.isDirectory() && dstConfFile.isFile() ) {
            logger.warn("{} is directory, but {} is file!", srcConfFile.getAbsolutePath(), dstConfFile.getAbsolutePath());
            return;
        }
        /**
         * 如果原路径为文件，而目标路径为目录，则不符合替换条件。
         */
        if( srcConfFile.isFile() && dstConfFile.isDirectory() ) {
            logger.warn("{} is file, but {} is directory!", srcConfFile.getAbsolutePath(), dstConfFile.getAbsolutePath());
            return;
        }
        
        /**
         * 原路径跟目标路径均为目录，进行递归拷贝。
         */
        File[] srcFiles = srcConfFile.listFiles();
        for(File srcFile : srcFiles) {
            String fileName = srcFile.getName();
            
            //读取目标配置文件路径
            File dstFile = new File(dstConfFile, fileName);
            //如果是目录，自动递归复制源结构至目标结构中
            if (!dstFile.exists()) {
                try {
                    if(srcFile.isDirectory())
                    {
                        dstFile.mkdir();
                    }else
                    {
                        dstFile.createNewFile();
                    }
                } catch (Exception e) {
                    logger.error("",e);
                    continue;
                }
            }
            recursionGetFullRJob(needDoJobs, srcFile, dstFile);
        }
    }
    
    public Task getFullReplaceTask() {
        return fullReplaceTask;
    }

    public Task getReplaceTask() {
        return replaceTask;
    }

    public Task getModifyTask() {
        return modifyTask;
    }
    
    public Task getDeleteTask() {
        return deleteTask;
    }
    
    public Task getAddTask() {
        return addTask;
    }
}
